tool
extends Spatial

class_name MapData

var level: Spatial = null

var tile_type_map: TileTypeMap = null setget set_tile_type_map

# Vector3 -> String
var map_data: Dictionary = {}
# Vector3 -> TileType
var data: Dictionary = {}
# Vector3 -> Array of Vector3
var blocked_area: Dictionary = {}
# Vector3 -> Spatial
var instances: Dictionary = {}

var lbound: Vector3 = Vector3()
var ubound: Vector3 = Vector3()

var configuration_warning: String = ""

# Vector3 -> Spatial
var flash_anim_inst: Dictionary = {}

func _get_configuration_warning():
	return configuration_warning

func _get_property_list():
	var prop_list: Array = []

	prop_list.append({
		name = "tile_type_map",
		type = TYPE_OBJECT,
		hint = PROPERTY_HINT_RESOURCE_TYPE,
		hint_string = "TileTypeMap",
	})

	prop_list.append({
		name = "map_data",
		type = TYPE_DICTIONARY,
		usage = PROPERTY_USAGE_NOEDITOR,
	})

	return prop_list

func get_tile(position: Vector3) -> TileType:
	return data.get(position, null)

func get_tile_id(position: Vector3) -> String:
	return map_data.get(position, "")

func set_tile(position: Vector3, tile: TileType) -> void:
	# remove blocked area
	var current_tile: TileType = get_tile(position)
	if current_tile != null:
		for dy in range(current_tile.blocked_area_lower, current_tile.blocked_area_upper):
			var v2: Vector3 = Vector3(position.x, position.y + dy, position.z)
			if blocked_area.has(v2):
				blocked_area[v2].erase(position)

	# remove instance
	# TODO: animation
	var current_inst: Spatial = instances.get(position, null)
	if current_inst != null:
# warning-ignore:return_value_discarded
		instances.erase(position)
		current_inst.queue_free()

	# remove map data
# warning-ignore:return_value_discarded
	map_data.erase(position)
# warning-ignore:return_value_discarded
	data.erase(position)

	if tile != null:
		# update bound
		lbound.x = min(lbound.x, position.x)
		lbound.y = min(lbound.y, position.y)
		lbound.z = min(lbound.z, position.z)
		ubound.x = max(ubound.x, position.x)
		ubound.y = max(ubound.y, position.y)
		ubound.z = max(ubound.z, position.z)

		# add new data
		map_data[position] = tile.id
		data[position] = tile

		# add blocked area
		for dy in range(tile.blocked_area_lower, tile.blocked_area_upper):
			var v2: Vector3 = Vector3(position.x, position.y + dy, position.z)
			if blocked_area.has(v2):
				blocked_area[v2].push_back(position)
			else:
				blocked_area[v2] = [position]
			lbound.y = min(lbound.y, v2.y)
			ubound.y = max(ubound.y, v2.y)

		if level == null:
			printerr("[MapData::set_tile] level node is empty, skip creating instance")
			return

		# add instance
		# TODO: animation
		var inst: Spatial = tile.create_instance()
		if inst:
			inst.translate(position)
			level.add_child(inst)
			instances[position] = inst

func set_tile_by_id(position: Vector3, tile_id: String) -> void:
	if tile_type_map == null:
		printerr("[MapData::set_tile_by_id] tile_type_map is empty!")
		return

	var tile: TileType = null
	if not tile_id.empty():
		tile = tile_type_map.lookup(tile_id)
		if tile == null:
			return

	set_tile(position, tile)

func recreate():
	data.clear()
	blocked_area.clear()
	instances.clear()
	lbound = Vector3()
	ubound = Vector3()

	configuration_warning = ""
	update_configuration_warning()

	if level != null:
		level.queue_free()
		level = null
	level = Spatial.new()
	add_child(level)

	if tile_type_map == null:
		if Engine.editor_hint:
			configuration_warning = "tile_type_map is empty!"
			update_configuration_warning()
		else:
			printerr("[MapData::recreate] tile_type_map is empty!")
		return

	if map_data.empty():
		return

	lbound = Vector3(1e+6, 1e+6, 1e+6)
	ubound = Vector3(-1e+6, -1e+6, -1e+6)
	for o in map_data:
		var v: Vector3 = (o as Vector3).floor()
		var grid_type_id: String = map_data[o] as String
		var tile_type: TileType = tile_type_map.lookup(grid_type_id)
		if tile_type != null:
			lbound.x = min(lbound.x, v.x)
			lbound.y = min(lbound.y, v.y)
			lbound.z = min(lbound.z, v.z)
			ubound.x = max(ubound.x, v.x)
			ubound.y = max(ubound.y, v.y)
			ubound.z = max(ubound.z, v.z)
			data[v] = tile_type

			for dy in range(tile_type.blocked_area_lower, tile_type.blocked_area_upper):
				var v2: Vector3 = Vector3(v.x, v.y + dy, v.z)
				if blocked_area.has(v2):
					blocked_area[v2].push_back(v)
				else:
					blocked_area[v2] = [v]
				lbound.y = min(lbound.y, v2.y)
				ubound.y = max(ubound.y, v2.y)

			var inst: Spatial = tile_type.create_instance()
			if inst != null:
				inst.translate(v)
				level.add_child(inst)
				instances[v] = inst

# Called when the node enters the scene tree for the first time.
func _ready():
	recreate()

func set_tile_type_map(ttm):
	tile_type_map = ttm

	if Engine.editor_hint:
		recreate()

func add_flash_animation(p: Vector3, anim: Dictionary):
	if flash_anim_inst.has(p):
		return

	var inst: Spatial = preload("res://HighlightOverlay.tscn").instance()
	inst.transform.origin = p
	inst.anim = anim
# warning-ignore:return_value_discarded
	inst.connect("animation_completed", self, "_on_flash_animation_completed", [p])
	flash_anim_inst[p] = inst
	add_child(inst)

func _on_flash_animation_completed(p: Vector3):
	if flash_anim_inst.has(p):
# warning-ignore:return_value_discarded
		flash_anim_inst.erase(p)
